package org.jsola.hr.service.impl;

import lombok.extern.slf4j.Slf4j;
import org.jsola.cache.ICache;
import org.jsola.common.ArrayKit;
import org.jsola.common.SafeKit;
import org.jsola.common.StrKit;
import org.jsola.contract.constant.PersonAreaType;
import org.jsola.core.Page;
import org.jsola.exception.ParamException;
import org.jsola.file.entity.FileVersionDO;
import org.jsola.file.exception.FileException;
import org.jsola.file.service.ISiteFileService;
import org.jsola.hr.common.CacheKeyKit;
import org.jsola.hr.common.ExampleKit;
import org.jsola.hr.common.FileDown;
import org.jsola.hr.constant.HrConstants;
import org.jsola.hr.constant.PersonalEventEnum;
import org.jsola.hr.dao.INameVerifiedDAO;
import org.jsola.hr.dto.NameVerifiedAddDTO;
import org.jsola.hr.dto.NameVerifiedUpdateDTO;
import org.jsola.hr.entity.EmpInfoDO;
import org.jsola.hr.entity.NameVerifiedDO;
import org.jsola.hr.exception.HrException;
import org.jsola.hr.provider.IContractProviderService;
import org.jsola.hr.provider.IUserProviderService;
import org.jsola.hr.query.NameVerifiedQuery;
import org.jsola.hr.service.IEmpInfoService;
import org.jsola.hr.service.INameVerifiedService;
import org.jsola.hr.service.IPersonalEventService;
import org.jsola.hr.vo.EmpInfoVO;
import org.jsola.hr.vo.NameVerifiedListVO;
import org.jsola.hr.vo.NameVerifiedVO;
import org.jsola.user.core.TokenUser;
import org.jsola.user.entity.UserDO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.FileSystemResource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import tk.mybatis.mapper.entity.Example;

import java.io.File;
import java.util.*;
import java.util.stream.Collectors;

import static org.jsola.hr.common.BaiduVerified.ID_CARD_SIDE_BACK;
import static org.jsola.hr.common.BaiduVerified.sample;

/**
 * 实名认证
 *
 * @author zhr
 */
@Slf4j
@Service("hrNameVerifiedServiceImpl")
public class NameVerifiedServiceImpl implements INameVerifiedService {

    @Autowired
    private INameVerifiedDAO nameVerifiedDAO;

    @Autowired
    private IContractProviderService contractProviderService;

    @Autowired
    private IUserProviderService userProviderService;

    @Autowired
    private IEmpInfoService empInfoService;

    @Autowired
    private ISiteFileService siteFileService;

    @Autowired
    private ICache cache;

    @Autowired
    private IPersonalEventService personalEventService;

    @Override
    @Transactional(value = HrConstants.TX, rollbackFor = Exception.class)
    public NameVerifiedVO save(NameVerifiedAddDTO nameVerifiedAddDTO, TokenUser tokenUser) {
        // 转DO
        NameVerifiedDO nameVerifiedDO = nameVerifiedAddDTO.to(NameVerifiedDO.class);
        // 保存
        nameVerifiedDO = save(nameVerifiedDO, tokenUser.getSiteId(), tokenUser.getUserId());

        return nameVerifiedDO.to(NameVerifiedVO.class);
    }

    @Override
    @Transactional(value = HrConstants.TX, rollbackFor = Exception.class)
    public NameVerifiedVO save(NameVerifiedAddDTO nameVerifiedAddDTO, String userId, TokenUser tokenUser) {

        // 转DO
        NameVerifiedDO nameVerifiedDO = nameVerifiedAddDTO.to(NameVerifiedDO.class);
        // 当前实名认证用户userID
        nameVerifiedDO.setUserId(userId);

        // 身份证末位小写x的情况
        String inputIdCard = changeIdCardx(nameVerifiedAddDTO.getIdCard());

        log.info("实名认证开始:idcard:{},body:{}", inputIdCard, nameVerifiedDO);

        // 得到用户的手机信息
        UserDO userDO = userProviderService.selectUserById(userId);
        HrException.notNull(userDO, "未查询到用户的信息");

        String empId = nameVerifiedAddDTO.getEmpInfoId();
        // 得到员工信息
        EmpInfoDO empInfoDO = empInfoService.selectDOById(nameVerifiedAddDTO.getEmpInfoId(), tokenUser.getSiteId());

        HrException.notNull(empInfoDO, "员工信息不存在");
        if (empInfoDO.getVerified()) {
            throw new ParamException("用户已实名认证完成,不需要重复认证");
        }

        // 是否锁定
        String idcardLockKey = CacheKeyKit.getIdCardLockVerified(empId, inputIdCard);
        if (cache.get(idcardLockKey) != null) {
            throw new ParamException("调用实名认证过于频繁,请稍后再试");
        }
        // 能否实名认证
        String idcardCanKey = CacheKeyKit.getIdCardCanVerified(empId, inputIdCard);
        if (cache.get(idcardCanKey) != null) {
            int sendCount = cache.get(idcardCanKey);
            if (sendCount > 5) {
                // 锁定30分钟
                cache.set(idcardLockKey, new Date(), 30 * 60 * 1000L);
                throw new ParamException("调用实名认证过于频繁,请稍后再试");
            }
            cache.set(idcardCanKey, sendCount + 1, 60 * 1000L);
        } else {
            cache.set(idcardCanKey, 1, 60 * 1000L);
        }
        log.info("传入的身份证人像面:{}", nameVerifiedAddDTO.getPositiveUrl());
        log.info("传入的身份证国徽面:{}", nameVerifiedAddDTO.getReverseUrl());
        // 下载非公开文件，并返回本地存储路径
        // 人像面
        String filePathPositive = getFilePath(nameVerifiedAddDTO.getPositiveUrl(), tokenUser);
        // 国徽面
        String filePathReverse = getFilePath(nameVerifiedAddDTO.getReverseUrl(), tokenUser);
        log.info("本地路径身份证人像面:{}", filePathPositive);
        log.info("本地路径身份证国徽面:{}", filePathReverse);
        // 调用百度识别身份证并返回识别结果
        // 人像面
        Map<String, String> positiveMap = sample(filePathPositive, inputIdCard);
        // 国徽面
        Map<String, String> reverseMap = sample(filePathReverse, inputIdCard, ID_CARD_SIDE_BACK);
        // 百度返回的身份证
        String bdIdCard = changeIdCardx(positiveMap.get("idcard"));

        // 百度返回的姓名
        String name = positiveMap.get("name");
        // 验证传过来的名字身份证跟图片的是否一致
        if (inputIdCard.equals(bdIdCard) && empInfoDO.getName().equals(name)) {

            nameVerifiedDO.setStatus(1);
            // 如果认证通过
            contractProviderService.verifyEmployee(userId, nameVerifiedAddDTO.getName(), userDO.getPhone(), nameVerifiedAddDTO.getIdCard(), PersonAreaType.MAINLAND.getValue(), tokenUser);

            // 修改
            EmpInfoDO empInfoUp = new EmpInfoDO();
            empInfoUp.setId(empInfoDO.getId());

            EmpInfoVO empInfoVO = empInfoService.selectById(empId, tokenUser.getSiteId());
            String annexIdCard = empInfoVO.getAnnexIdCard();
            List<String> annexIdCardList = ArrayKit.split(annexIdCard);
            if (CollectionUtils.isEmpty(annexIdCardList)) {
                annexIdCardList = new ArrayList<>();
            }
            annexIdCardList.add(nameVerifiedAddDTO.getPositiveUrl());
            annexIdCardList.add(nameVerifiedAddDTO.getReverseUrl());
            // 去重，非空
            String annexIdCards = annexIdCardList.stream()
                    .distinct()
                    .filter(StrKit::isNotEmpty)
                    .collect(Collectors.joining(","));
            // 身份证附件
            empInfoUp.setAnnexIdCard(annexIdCards);
            // 身份证保持一致
            empInfoUp.setIdCard(bdIdCard);
            // 修改员工实名认证状态;已认证
            empInfoUp.setVerified(true);
            Map<Object, Object> idcardInfoMap = getInfo(inputIdCard);
            empInfoUp.setBirthday(SafeKit.getString(idcardInfoMap.get("birthday")));
            empInfoUp.setGender(SafeKit.getInteger(idcardInfoMap.get("sexcode")));
            empInfoUp.setEmpAge(SafeKit.getInteger(idcardInfoMap.get("age")));
            empInfoUp.setNativeLocation(positiveMap.get("address"));
            empInfoUp.setNation(positiveMap.get("nation") + "族");

            empInfoUp.setIssuedBy(reverseMap.get("issuedBy"));
            empInfoUp.setCardStartDate(reverseMap.get("cardStartDate"));
            empInfoUp.setCardEndDate(reverseMap.get("cardEndDate"));

            empInfoService.updateByIdSelective(empInfoUp, tokenUser.getSiteId(), userId);
        } else {
            throw new HrException("实名认证信息，跟图片扫描信息不一致，认证失败");
        }
        // 保存
        nameVerifiedDO = save(nameVerifiedDO, tokenUser.getSiteId(), userId);

        NameVerifiedVO verifiedVO = nameVerifiedDO.to(NameVerifiedVO.class);

        personalEventService.autoSave(empInfoDO.to(EmpInfoVO.class), tokenUser, PersonalEventEnum.VERIFIED.getDesc()
                , PersonalEventEnum.VERIFIED.getValue(), PersonalEventEnum.VERIFIED.getDesc());

        // 验证成功的,放入缓存
        cache.delKey(idcardCanKey);
        cache.delKey(idcardLockKey);

        return verifiedVO;
    }

    /**
     * 变更身份证末位,末位小写x换成大写X
     *
     * @param idCard 身份证
     * @return 身份证
     */
    private String changeIdCardx(String idCard) {
        // 去除空格
        idCard = idCard.replaceAll(" ", "");
        if (idCard.endsWith("x")) {
            return idCard.replaceAll("x", "X");
        } else {
            return idCard;
        }
    }

    /**
     * 获取身份证信息
     */
    private Map<Object, Object> getInfo(String certificateNo) {
        String sexcode = "";
        String birthday = "";
        String age = "";

        int year = Calendar.getInstance().get(Calendar.YEAR);
        char[] number = certificateNo.toCharArray();
        boolean flag = true;

        if (number.length == 15) {
            for (int x = 0; x < number.length; x++) {
                if (!flag) {
                    return new HashMap<>();
                }
                flag = Character.isDigit(number[x]);
            }
        } else if (number.length == 18) {
            for (int x = 0; x < number.length - 1; x++) {
                if (!flag) {
                    return new HashMap<>();
                }
                flag = Character.isDigit(number[x]);
            }
        }

        if (flag && certificateNo.length() == 15) {
            birthday = "19" + certificateNo.substring(6, 8) + "-"
                    + certificateNo.substring(8, 10) + "-"
                    + certificateNo.substring(10, 12);
            sexcode = Integer.parseInt(certificateNo.substring(certificateNo.length() - 3, certificateNo.length())) % 2 == 0 ? "2" : "1";
            age = (year - Integer.parseInt("19" + certificateNo.substring(6, 8))) + "";
        } else if (flag && certificateNo.length() == 18) {
            birthday = certificateNo.substring(6, 10) + "-"
                    + certificateNo.substring(10, 12) + "-"
                    + certificateNo.substring(12, 14);
            sexcode = Integer.parseInt(certificateNo.substring(certificateNo.length() - 4, certificateNo.length() - 1)) % 2 == 0 ? "2" : "1";
            age = (year - Integer.parseInt(certificateNo.substring(6, 10))) + "";
        }

        Map<Object, Object> map = new HashMap<>();
        map.put("sexcode", sexcode);
        map.put("age", age);
        map.put("birthday", birthday);
        return map;
    }

    /**
     * 获取图片本地路径，支持网络图片和本系统的校验权限图片
     *
     * @param url       图片url
     * @param tokenUser 当前用户
     */
    private String getFilePath(String url, TokenUser tokenUser) {
        String[] split = url.split("/");
        // 根据“/”分割的倒数第二段
        String fileId = split[split.length - 2];
        Long aLong = SafeKit.getLong(fileId);
        String filePath;
        // 无法解析为长整数则视为网络图片
        if (aLong == null) {
            return FileDown.getFilePath(url);
        } else {
            FileVersionDO fileVersionDO = siteFileService.download(url, tokenUser);
            HrException.notNull(fileVersionDO, "图片文件不存在");
            filePath = fileVersionDO.getTempFilePath();
        }
        FileSystemResource resource = new FileSystemResource(filePath);
        if (!resource.exists()) {
            throw new FileException("系统错误，文件不存在或已被删除");
        }
        File file = resource.getFile();
        File resultFile = FileDown.resizeImage(file, 8, 8);
        return resultFile.getAbsolutePath();
    }


    @Override
    @Transactional(value = HrConstants.TX, rollbackFor = Exception.class)
    public int update(NameVerifiedUpdateDTO nameVerifiedUpdateDTO, TokenUser tokenUser) {
        //转DO
        NameVerifiedDO nameVerifiedDO = nameVerifiedUpdateDTO.to(NameVerifiedDO.class);
        //根据主键更新，只更新非null值
        return updateByIdSelective(nameVerifiedDO, tokenUser.getSiteId(), tokenUser.getUserId());
    }

    @Override
    @Transactional(value = HrConstants.TX, rollbackFor = Exception.class)
    public int deleteByIds(TokenUser tokenUser, Long... nameVerifiedIds) {
        return deleteByIds(tokenUser.getSiteId(), tokenUser.getUserId(), (Object[]) nameVerifiedIds);
    }


    @Override
    public NameVerifiedVO selectById(Long nameVerifiedId, String siteId) {
        NameVerifiedDO nameVerifiedDO = selectDOById(nameVerifiedId, siteId);
        if (nameVerifiedDO == null) {
            return null;
        }
        return nameVerifiedDO.to(NameVerifiedVO.class);
    }

    @Override
    public List<NameVerifiedListVO> select(NameVerifiedQuery nameVerifiedQuery, String siteId) {
        List<NameVerifiedDO> nameVerifiedDOList = selectDO(nameVerifiedQuery, siteId);
        if (CollectionUtils.isEmpty(nameVerifiedDOList)) {
            return nameVerifiedDOList == null ? null : new ArrayList<>();
        }
        return nameVerifiedDOList.stream()
                .map(nameVerifiedDO -> nameVerifiedDO.to(NameVerifiedListVO.class))
                .collect(Collectors.toList());
    }

    @Override
    public int selectCount(NameVerifiedQuery nameVerifiedQuery, String siteId) {
        Example example = buildExample(nameVerifiedQuery, siteId);
        return nameVerifiedDAO.selectCountByExample(example);
    }

    @Override
    public Page<NameVerifiedListVO> selectPage(NameVerifiedQuery nameVerifiedQuery, String siteId) {
        Example example = buildExample(nameVerifiedQuery, siteId);
        Page<NameVerifiedDO> page = nameVerifiedDAO.selectPageByExample(example,
                nameVerifiedQuery.getPageNo(),
                nameVerifiedQuery.getPageSize());

        return page.to(NameVerifiedListVO.class);
    }

    @Override
    public NameVerifiedDO selectDOById(Long nameVerifiedId, String siteId) {
        return listById(nameVerifiedId, siteId);
    }

    @Override
    public List<NameVerifiedDO> selectDO(NameVerifiedQuery nameVerifiedQuery, String siteId) {
        Example example = buildExample(nameVerifiedQuery, siteId);
        return nameVerifiedDAO.selectByExample(example);
    }

    @Override
    public Boolean isUserVerified(String userId, String siteId) {
        Example example = new Example(NameVerifiedDO.class);
        example.and()
                .andEqualTo("userId", userId)
                .andEqualTo("siteId", siteId);
        return !CollectionUtils.isEmpty(nameVerifiedDAO.selectByExample(example));
    }

    @Override
    public List<NameVerifiedDO> selectByUserIds(List<String> userIds, String siteId) {
        if (CollectionUtils.isEmpty(userIds)) {
            return new ArrayList<>();
        }
        Example example = new Example(NameVerifiedDO.class);
        example.and()
                .andIn("userId", userIds)
                .andEqualTo("siteId", siteId);
        return nameVerifiedDAO.selectByExample(example);
    }

    /**
     * 根据查询参数，构建example
     *
     * @param nameVerifiedQuery 查询参数
     * @param siteId            所属站点id
     * @return example
     */
    private Example buildExample(NameVerifiedQuery nameVerifiedQuery, String siteId) {
        Example example = new Example(NameVerifiedDO.class);
        example.and()
                .andEqualTo("siteId", siteId);
        if (nameVerifiedQuery.getUserId() != null) {
            example.and().andEqualTo("userId", nameVerifiedQuery.getUserId());
        }
        if (nameVerifiedQuery.getName() != null) {
            example.and().andEqualTo("name", nameVerifiedQuery.getName());
        }
        if (nameVerifiedQuery.getIdCard() != null) {
            example.and().andEqualTo("idCard", nameVerifiedQuery.getIdCard());
        }
        if (nameVerifiedQuery.getPositiveUrl() != null) {
            example.and().andEqualTo("positiveUrl", nameVerifiedQuery.getPositiveUrl());
        }
        if (nameVerifiedQuery.getReverseUrl() != null) {
            example.and().andEqualTo("reverseUrl", nameVerifiedQuery.getReverseUrl());
        }
        if (nameVerifiedQuery.getStatus() != null) {
            example.and().andEqualTo("status", nameVerifiedQuery.getStatus());
        }
        // 排序
        ExampleKit.setExampleOrder(example, nameVerifiedQuery.getOrders());
        return example;
    }
}




